#ifndef __MONITOR_RESERVAS_HPP__
#define __MONITOR_RESERVAS_HPP__

#include "common.hpp"

#ifndef MAX_CLIENTES
#define MAX_CLIENTES 20
#endif

#ifndef MAX_DIAS
#define MAX_DIAS 7
#endif

typedef struct {
	size_t registro[MAX_DIAS][MAX_CLIENTES];
} sReservas;

template <class R, class M, size_t DIAS, size_t CLIENTES> 
class monitor_reservas_base {
	protected:
		R reservas;
		M mutex;
		bool inicializado;
		std::string buffer;

		monitor_reservas_base (const monitor_reservas_base& other);
		monitor_reservas_base& operator=(const monitor_reservas_base& other);
	
		inline void resumen_dia_y_cliente_unsafe(size_t dia, size_t cliente_id) {
			std::stringstream ss;
			size_t reservas_del_dia = 0;
			size_t reservas_del_dia_del_cliente;
			std::map<size_t,size_t> aux_reservas;

			for (size_t c = 0; c<CLIENTES; ++c)
				reservas_del_dia+= reservas->registro[dia][c];
			reservas_del_dia_del_cliente = reservas->registro[dia][cliente_id-1];

			if (cliente_id > 0) {
				for (size_t d = 0; d < DIAS; ++d) {
					size_t aux = reservas->registro[d][cliente_id-1];
					//if ( aux > 0 )
						aux_reservas[d] = aux;
				}
			}

			buffer.clear();
			// Recolecte todos los datos para mostrarlos, puedo liberar la estructura.

			ss << "Cliente_id: " << cliente_id << " Día: " << dia << " Reservas: " << reservas_del_dia_del_cliente;
			ss << " Reservas del día: " << reservas_del_dia << ". Calendario: " << "[ ";
			for (std::map<size_t,size_t>::const_iterator it = aux_reservas.begin(); it != aux_reservas.end(); ++it) {
				ss << (it->first) << "=" << it->second << " ";
			}
			ss << "].";
	
			buffer = ss.str();
		}

	public:
		monitor_reservas_base(key_t reservas_key, key_t mutex_key, size_t mutex_num):
			reservas(reservas_key), mutex(mutex_key, mutex_num) {
			inicializado = false;
		}

		inline int get(int mode) {
			int error = mutex.get(mode);
			if (error == 0)
				error = reservas.get(mode);
			inicializado = (error == 0);
			return error;
		}

		// Si se quiere modificar la inicialización se puede heredar y cambiar la forma en que se inicializa.
		// El initValue es un string para permitir cargar de un archivo.
		inline virtual bool init(const std::string& initValue) {
			bool exito = false;
			size_t value, dia, cliente;
			if (inicializado) {
				mutex.init(1);
				value = parse(initValue,0);
				mutex.wait();
					for (cliente = 0; cliente<CLIENTES; ++cliente) {
						for (dia = 0; dia < DIAS; ++dia) {
							reservas->registro[dia][cliente] = value;
						}
					}
				mutex.signal();
				exito = true;
			}
			return exito;
		}

		inline bool usar_reserva(size_t dia, size_t cliente_id, std::string* buffer) {
			bool exito = false;
			if ( (inicializado) && (dia <= DIAS) && (cliente_id-1 <= CLIENTES) ) {
				mutex.wait();
					exito = reservas->registro[dia][cliente_id-1] > 0;
					if (exito)
						reservas->registro[dia][cliente_id-1]--;

					if (buffer != NULL) {
						resumen_dia_y_cliente_unsafe(dia,cliente_id);
						*buffer = this->buffer;
					}
				mutex.signal();
			}
			return exito;
		}

		inline bool tiene_reserva(size_t dia, size_t cliente_id) {
			bool existe_reserva = false;
			if ( (inicializado) && (dia <= DIAS) && (cliente_id-1 <= CLIENTES) ) {
				mutex.wait();
					existe_reserva = reservas->registro[dia][cliente_id-1] > 0;
				mutex.signal();
			}
			return existe_reserva;
		}

		inline bool hacer_reserva(size_t dia, size_t cliente_id, std::string* buffer) {
			bool exito = false;
			if ( (inicializado) && (dia <= DIAS) && (cliente_id-1 <= CLIENTES) ) {
				mutex.wait();
					reservas->registro[dia][cliente_id-1]++;

					if (buffer != NULL) {
						resumen_dia_y_cliente_unsafe(dia,cliente_id);
						*buffer = this->buffer;
					}
				mutex.signal();
			}
			return exito;
		}

		inline const std::string& calendario () {
			if (inicializado) {
				mutex.wait();
					std::ostringstream ss;

					ss << "Calendario: [\n";
					ss << "Día\\Cliente:\t{ ";
					for (size_t c=0; c<CLIENTES; ++c)
						ss << std::setw(2) << (c+1) << " ";
					ss << "}\n";

					for (size_t d=0; d<DIAS; ++d) {
						ss << std::setw(7) << d << "/" << std::setw(3) << DIAS << ":\t{ ";
						for (size_t c=0; c<CLIENTES; ++c)
							ss << std::setw(2) << reservas->registro[d][c] << " ";
						ss << "}\n";
					}
					ss << "]";
					buffer = ss.str();
				mutex.signal();

			} else
				buffer.clear();

			return buffer;
		}


		inline const std::string& resumen_dia_y_cliente (size_t dia, size_t cliente_id) {
			if ( (inicializado) && (dia <= DIAS) && (cliente_id-1 <= CLIENTES) ) {
				mutex.wait();

					resumen_dia_y_cliente_unsafe(dia,cliente_id);
				mutex.signal();

			} else
				buffer.clear();

			return buffer;
		}

		inline int destroy() {
			int error = reservas.destroy();
			error+= mutex.destroy();
			return error;
		}
	
		virtual ~monitor_reservas_base() {}

}; // class monitor_reservas_base


typedef monitor_reservas_base< ipc::shared_memory<sReservas>,
						 ipc::semaphore,
						 MAX_DIAS, MAX_CLIENTES
> monitor_reservas;

typedef monitor_reservas_base< ipc::shared_memory_with_exceptions<sReservas>,
						 ipc::semaphore_with_exceptions,
						 MAX_DIAS, MAX_CLIENTES
> monitor_reservas_with_exceptions;

#endif /* __MONITOR_RESERVAS_HPP__ */

